/*
 * Copyright (C) 2006 Moritz Tuerk <brotkasten@users.sourceforge.net>
 * 
 * This program is free software; you can redistribute it and/or modify it under the terms 
 * of the GNU General Public License as published by the Free Software Foundation; either 
 * version 2 of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful, 
 * but WITHOUT ANY WARRANTY; without even the implied 
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
 * See the GNU General Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License along with 
 *  this program; if not, write to the Free Software Foundation, 
 *  Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
 */
package mo.network.server;

import java.io.IOException;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NetworkInterface;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Vector;

import mo.network.NetworkPeer;
import mo.network.client.listeners.IConnectListener;
import mo.network.client.listeners.IReceiver;
import mo.network.client.listeners.ISender;
import mo.network.client.listeners.ReceiverEventQueue;
import mo.network.client.listeners.SenderEvent;
import mo.network.client.listeners.SenderEventQueue;
import mo.network.server.data.Packet;

/**
 * The network server is an implementation of a socket listener, that spawns
 * for
 * each incoming connect a new handler thread ({@link ServerThread}). This can
 * be used for communication between a server an multiple clients.
 * 
 * @author brotkasten
 */
public class NetworkServer implements Runnable, NetworkPeer {
	/**
	 * A Thread that sends ping packets to a client
	 * 
	 * @author brotkasten
	 */
	private class PingThread extends Thread {
		/**
		 * saves the interval between 2 ping packets
		 */
		private final long pingIntervall;

		/**
		 * saves the server thread to queue the ping packets to
		 */
		private final ServerThread serverThread;

		/**
		 * flag that indicates if this ping thread is alive
		 */
		private boolean stillAlive;

		public PingThread(final Socket clientSock, final long pingIntervall) {
			this.serverThread = NetworkServer.this.clientTable.get(clientSock);
			this.stillAlive = false;
			this.pingIntervall = pingIntervall;
		}

		/**
		 * (overridden)
		 * 
		 * @see java.lang.Thread#run()
		 */
		@Override
		public void run() {
			if (this.stillAlive) {
				return;
			}
			this.stillAlive = true;
			while (this.stillAlive && this.serverThread.stillAlive) {
				try {
					try {
						Thread.sleep(this.pingIntervall);
					} catch (final InterruptedException ire) {
						// nothing to do
					}
					final Packet pingPacket =
							new Packet("PING".getBytes(), this.serverThread
									.getClientSocket());

					pingPacket.setType(Packet.PACKET_TYPE_PING);
					pingPacket.setPriority(Packet.PACKET_PRIORITY_MAX - 2);

					this.serverThread.queue(pingPacket);

				} catch (final Exception e) {
					System.err.println("[UHH OHH ... PING THREAD blew up!");
					e.printStackTrace();
					this.stillAlive = false;
					NetworkServer.this.pingThreads.remove(this.serverThread);
				}
			}
			super.run();
		}
	}

	/**
	 * constant value for {@link #selectInterface(NetworkInterface, int)},
	 * which
	 * defines, that the inet4 address is to be taken (if available)
	 */
	public final static int ADDR_TYPE_INET4 = 0;

	/**
	 * constant value for {@link #selectInterface(NetworkInterface, int)},
	 * which
	 * defines, that the inet6 address is to be taken (if available)
	 */
	public final static int ADDR_TYPE_INET6 = 1;

	/**
	 * amount of bytes downloaded in the last time period
	 */
	private static int currentDownload = 0;

	/**
	 * amount of bytes uploaded in the last period of time
	 */
	private static int currentUpload = 0;

	/**
	 * time of last download
	 */
	private static long lastDownload = System.currentTimeMillis();

	/**
	 * time of last upload
	 */
	private static long lastUpload = System.currentTimeMillis();

	/**
	 * maximum amount of bytes to download per period of time (e.g. in a
	 * second)
	 */
	private static int maxDownload = 64 * 1024;

	/**
	 * maximum amount of bytes to upload per period of time (e.g. in a second)
	 */
	private static int maxUpload = 14 * 1024;

	/**
	 * duration of a period for up and download checking.
	 */
	private static final int PERIOD = 1000;

	/**
	 * Use this method to get the maximum number of bytes (per second) to read
	 * from all clients.
	 * 
	 * @return the maximum number bytes of download bandwidth the network
	 *         server
	 *         uses
	 */
	public static int getMaxDownload() {
		return NetworkServer.maxDownload;
	}

	/**
	 * Use this method to get the maximum number of bytes (per second) to send
	 * to all clients from the server.
	 * 
	 * @return the maximum number bytes of upload bandwidth the network server
	 *         uses
	 */
	public static int getMaxUpload() {
		return NetworkServer.maxUpload;
	}

	/**
	 * This method can be called to release reserved, but unused download
	 * bandwidth.
	 * 
	 * @param size
	 *            saves the number of bytes to be added back to the available
	 *            download bandwidth!
	 */
	public static synchronized void releaseDownload(final int size) {
		if (NetworkServer.currentDownload - size > 0) {
			NetworkServer.currentDownload -= size;
		}
	}

	/**
	 * This method can be called to release reserved, but unused upload
	 * bandwidth.
	 * 
	 * @param size
	 *            saves the number of bytes to be added back to the available
	 *            upload bandwidth!
	 */
	public static synchronized void releaseUpload(final int size) {
		if (NetworkServer.currentUpload - size > 0) {
			NetworkServer.currentUpload -= size;
		}
	}

	/**
	 * This can be called from the server threads to reserve some download
	 * bandwidth.
	 * 
	 * @param size
	 *            saves the size of download bandwidth to reserve
	 * @return <code>false</code> if the bandwidth is currently not available,
	 *         else <code>true</code>
	 */
	public static synchronized boolean reserveDownload(final int size) {
		final long now = System.currentTimeMillis();

		if (now - NetworkServer.lastDownload >= NetworkServer.PERIOD) {
			NetworkServer.currentDownload = 0;
			NetworkServer.lastDownload = now;
		}

		if (NetworkServer.currentDownload + size < NetworkServer.maxDownload) {
			NetworkServer.currentDownload += size;
			return true;
		}

		return false;

	}

	/**
	 * This can be called from the server threads to reserve some upload
	 * bandwidth.
	 * 
	 * @param size
	 *            saves the size of bandwidth to reserver
	 * @return <code>true</code> if reservation is OK, else <code>false</code>
	 */
	public static synchronized boolean reserveUpload(final int size) {
		final long now = System.currentTimeMillis();

		if (now - NetworkServer.lastUpload >= NetworkServer.PERIOD) {
			NetworkServer.currentUpload = 0;
			NetworkServer.lastUpload = now;
		}

		if (NetworkServer.currentUpload + size < NetworkServer.maxUpload) {
			NetworkServer.currentUpload += size;
			return true;
		}
		System.out.println("Reserve Failed " + NetworkServer.currentUpload
				+ " -- " + NetworkServer.maxUpload);
		return false;
	}

	/**
	 * Call this method to restrict the maximum number of bytes of download
	 * bandwidth of the server.
	 * 
	 * @param maxBytes
	 *            the maximum number of bytes the server reads from all
	 *            clients
	 */
	public static void setMaxDownload(final int maxBytes) {
		NetworkServer.maxDownload = maxBytes;
	}

	/**
	 * Call this method to restrict the maximum number of bytes of upload
	 * bandwidth of the server.
	 * 
	 * @param maxBytes
	 *            the maximum number of bytes the server sends to all clients
	 */
	public static void setMaxUpload(final int maxBytes) {
		NetworkServer.maxUpload = maxBytes;
	}

	/**
	 * saves the table with all connected clients by their sockets
	 */
	private final Hashtable<Socket, ServerThread> clientTable;

	/**
	 * saves all connection listeners
	 */
	private final Vector<IConnectListener> connectListeners;

	/**
	 * saves the critical error that made the network server blow up
	 */
	private Exception criticalError;

	/**
	 * {@link IReceiver#packetReceived(Packet)} events for registered for
	 * {@linkplain #addReceiveListener(IReceiver) global listeners}
	 */
	private final ReceiverEventQueue globalReceiverQueue;

	private final SenderEventQueue globelSenderQueue;

	/**
	 * the list of all available nics on the server...
	 */
	private NetworkInterface interfaceList[];

	/**
	 * the address the server listens to
	 */
	private InetAddress listenAddress;

	/**
	 * server listen port
	 */
	private int listenPort;

	/**
	 * saves the ping threads for the server threads. The ping threads are
	 * threads that queue a packet for a client while the client is connected
	 * to
	 * the server.
	 */
	private final Hashtable<ServerThread, PingThread> pingThreads;

	/**
	 * this flag shows if the server is still running
	 */
	boolean stillAlive;

	/**
	 * this flag determines if the server is waiting for clients to connect
	 */
	private boolean waitForConnects;

	/**
	 * set defaults
	 */
	public NetworkServer() {
		this.listenPort = 15000;

		NetworkServer.maxUpload = 14 * 1024; // 14KiB
		NetworkServer.maxDownload = 128 * 1024; // 128KiB

		// timestamps to now
		NetworkServer.lastUpload = System.currentTimeMillis();
		NetworkServer.lastDownload = System.currentTimeMillis();

		// no current up or download
		NetworkServer.currentUpload = 0;
		NetworkServer.currentDownload = 0;

		// set up listeners
		this.connectListeners = new Vector<IConnectListener>();
		this.globalReceiverQueue = new ReceiverEventQueue();
		this.globelSenderQueue = new SenderEventQueue();

		this.clientTable = new Hashtable<Socket, ServerThread>();
		this.pingThreads = new Hashtable<ServerThread, PingThread>();

		// flag-- server running
		this.stillAlive = false;
		this.waitForConnects = true;
	}

	/**
	 * call this method to add a listener that gets notified about incoming
	 * client connects. You cannot add a listener more than one time.
	 * 
	 * @param listener
	 *            the listener to be added to the list of connection listeners
	 */
	public void addConnectListener(final IConnectListener listener) {
		if (!this.connectListeners.contains(listener)) {
			this.connectListeners.add(listener);
		}
	}

	/**
	 * Call this method to add a new listener for all client packets. Each
	 * listener can be registered only once.
	 * 
	 * @param receiveListener
	 *            the listener for all incoming packets
	 */
	public void addReceiveListener(final IReceiver receiveListener) {
		this.globalReceiverQueue.addIReceiver(receiveListener);
	}

	/**
	 * Call this method to add a new send listener for all client packets.
	 * Each
	 * listener can be registered only once.
	 * 
	 * @param sendListener
	 *            the listener for all incoming packets
	 */
	public synchronized void addSendListener(final ISender sendListener) {
		this.globelSenderQueue.addISender(sendListener);
	}

	/**
	 * Adds a new listener for incoming packets for the supplied client
	 * 
	 * @param listener
	 *            saves the listener
	 * @param clientSocket
	 *            saves the client socket for this listener
	 */
	public synchronized void addSpecificRecieveListener(
			final IReceiver listener, final Socket clientSocket) {
		final ServerThread theThread = this.clientTable.get(clientSocket);
		theThread.addReceiveListener(listener);
	}

	/**
	 * Adds a new listener for outgoing packets for the supplied client
	 * 
	 * @param listener
	 *            saves the listener
	 * @param clientSocket
	 *            saves the client socket for this listener
	 */
	public synchronized void addSpecificSendListener(final ISender listener,
			final Socket clientSocket) {
		final ServerThread theThread = this.clientTable.get(clientSocket);
		theThread.addSendListener(listener);
	}

	/**
	 * This method is called start a new thread for a new accepted client;
	 * 
	 * @param newClient
	 * @return the created handler for the specific socket.
	 */
	private synchronized ServerThread connectClient(final Socket newClient) {
		try {
			final ServerThread newThread = new ServerThread(this, newClient);
			this.clientTable.put(newClient, newThread);
			return newThread;
		} catch (final Exception e) {
			System.err.println("[HMMPF... CLIENT DID NOT CONNECT]");
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * Use this method to start a ping thread for the specified client
	 * 
	 * @param clientSock
	 *            the client socket, that identifies the client to start the
	 *            ping thread for.
	 * @param pingIntervall
	 *            defines the intervall to wait between two ping packets
	 */
	public synchronized void createPingThread(final Socket clientSock,
			final long pingIntervall) {
		final ServerThread serverThread = this.clientTable.get(clientSock);

		if (!this.pingThreads.containsKey(serverThread)) {
			final PingThread pt = new PingThread(clientSock, pingIntervall);
			pt.start();

			this.pingThreads.put(serverThread, pt);
		}
	}

	/**
	 * Call this method to stop a ping thread for the given client
	 * 
	 * @param clientSock
	 *            saves the client socket that identifies the client to stop
	 *            the
	 *            ping thread for.
	 */
	public synchronized void destroyPingThread(final Socket clientSock) {
		final ServerThread serverThread = this.clientTable.get(clientSock);

		final PingThread pt = this.pingThreads.get(serverThread);

		if (pt != null) {
			pt.stillAlive = false;
			this.pingThreads.remove(serverThread);
		}
	}

	/**
	 * This is used to disconnect a running client. This also stops the
	 * corrosponding server thread
	 * 
	 * @param oldClient
	 *            saves the client socket of the client to disconnect
	 */
	public synchronized void disconnectClient(final Socket oldClient) {
		final ServerThread theThread = this.clientTable.remove(oldClient);
		if (theThread != null) {
			theThread.stillAlive = false;
			this.fireClientDisconnected(oldClient);
		}
	}

	/**
	 * This methode is called when a new client connects to the server. It
	 * fires
	 * the client connected events to all connection listeners
	 * 
	 * @param newClient
	 *            the new client that connected
	 */
	protected void fireClientConnected(final Socket newClient) {
		if (newClient != null) {
			final Iterator<IConnectListener> listenerIter =
					this.connectListeners.iterator();
			while (listenerIter.hasNext()) {
				listenerIter.next().clientConnected(newClient);
			}
		}
	}

	/**
	 * This method is called when a client is removed from the list of active
	 * clients from this server. It fires the client disconnected events to
	 * all
	 * the connection listeners
	 * 
	 * @param oldClient
	 *            the client to be disconnected
	 */
	protected void fireClientDisconnected(final Socket oldClient) {
		if (oldClient != null) {
			final Iterator<IConnectListener> listenerIter =
					this.connectListeners.iterator();
			while (listenerIter.hasNext()) {
				listenerIter.next().clientDisconnected(oldClient);
			}
		}
	}

	/**
	 * is called internally to notify the global packet send listeners that a
	 * packet failed
	 * 
	 * @param thePacket
	 *            the packet that failed
	 */
	void fireGlobalPacketFailed(final Packet thePacket) {
		this.globelSenderQueue.enqueue(thePacket, SenderEvent.PACKET_FAILED);
	}

	/**
	 * is called internally to notify the global packet receive listeners that
	 * a
	 * packet was received
	 * 
	 * @param thePacket
	 *            the packet that was received
	 */
	void fireGlobalPacketReceived(final Packet thePacket) {
		this.globalReceiverQueue.enqueue(thePacket);
	}

	/**
	 * is called internally to notify the global packet send listeners that a
	 * packet was sent
	 * 
	 * @param thePacket
	 *            the packet that was sent
	 */
	void fireGlobalPacketSent(final Packet thePacket) {
		this.globelSenderQueue.enqueue(thePacket, SenderEvent.PACKET_SENT);
	}

	/**
	 * Call this method to get all client sockets connected to this server.
	 * 
	 * @return an array of client sockets
	 */
	public synchronized Socket[] getClients() {
		final Enumeration<Socket> clientEnum = this.clientTable.keys();
		final Socket[] result = new Socket[this.clientTable.size()];

		int index = 0;
		while (clientEnum.hasMoreElements()) {
			result[index++] = clientEnum.nextElement();
		}

		return result;
	}

	/**
	 * You can call this method to get the critical error that made the server
	 * blow up (in the {@link #run()} method)
	 * 
	 * @return the exception that made the thread stop.
	 */
	public Exception getCriticalError() {
		return this.criticalError;
	}

	/**
	 * returns the list of available network interfaces on this machine.
	 * 
	 * @return all available nics on this server
	 */
	public NetworkInterface[] getInterfaces() {
		return this.interfaceList;
	}

	/**
	 * <b>Must be called before starting the server</b>. This will initialize
	 * some internal members, for instance the list of available network
	 * interfaces.
	 * 
	 * @throws SocketException
	 *             if the request for the available network interfaces fails.
	 */
	public void initServer() throws SocketException, IOException {
		final Enumeration<NetworkInterface> netEnum =
				NetworkInterface.getNetworkInterfaces();
		final ArrayList<NetworkInterface> allInterfaces =
				new ArrayList<NetworkInterface>();

		while (netEnum.hasMoreElements()) {
			allInterfaces.add(netEnum.nextElement());
		}

		this.interfaceList =
				allInterfaces
						.toArray(new NetworkInterface[allInterfaces.size()]);
	}

	/**
	 * Call this method to check if the server is still running
	 * 
	 * @return <code>true</code> if the network server is running
	 */
	public boolean isAlive() {
		return this.stillAlive;
	}

	/**
	 * Call this method to check if the network server is still running
	 * 
	 * @return <code>true</code> if the network server {@link #run()} method
	 *         is
	 *         still running
	 */
	public boolean isStillAlive() {
		return this.stillAlive;
	}

	/**
	 * Call this method to kill the network server and all the server threads
	 */
	public void killServer() {
		System.out.println("KILLING SERVER...");
		this.stillAlive = false;
	}

	/**
	 * Call this method to queue a packet for all clients.
	 * 
	 * @param toSend
	 *            saves the packet to send to all clients
	 */
	public synchronized void queueForAll(final Packet toSend) {
		for (final ServerThread next : this.clientTable.values()) {
			final Packet reallyQueued =
					new Packet(toSend.getPayLoad(), next.getClientSocket());
			next.queue(reallyQueued);
		}
	}

	/**
	 * Call this method to send a new Packet to the client.
	 * 
	 * @param toSend
	 *            saves the packet to send
	 */
	public synchronized void queueUpload(final Packet toSend) {
		final ServerThread theThread = this.clientTable.get(toSend.getSocket());
		theThread.queue(toSend);
	}

	/**
	 * call this method to remove a connection listener from the list of
	 * connections listeners
	 * 
	 * @param listener
	 *            the listener to be removed
	 */
	public void removeConnectListener(final IConnectListener listener) {
		this.connectListeners.remove(listener);
	}

	/**
	 * Call this method to remove a listener that listens to all clients for
	 * sent packets.
	 * 
	 * @param receiveListener
	 *            the listener to be removed from the list of global receive
	 *            listeners
	 */
	public void removeReceiveListener(final IReceiver receiveListener) {
		this.globalReceiverQueue.removeIReceiver(receiveListener);
	}

	/**
	 * Call this method to remove a listener that listens to all clients for
	 * sent packets.
	 * 
	 * @param sendListener
	 *            the listener to be removed from the list of global send
	 *            listeners.
	 */
	public synchronized void removeSendListener(final ISender sendListener) {
		this.globelSenderQueue.removeISender(sendListener);
	}

	/**
	 * Removes the given listener from the list of receive listeners.
	 * 
	 * @param receiveListener
	 *            the listener to be removed from the list of receive
	 *            listeners.
	 * @param clientSock
	 *            the client socket that identifies the client the listener
	 *            listens to.
	 */
	public synchronized void removeSpecificReceiveListener(
			final IReceiver receiveListener, final Socket clientSock) {

		final ServerThread serverThread = this.clientTable.get(clientSock);
		if (serverThread != null) {
			serverThread.removeReceiveListener(receiveListener);
		}

	}

	/**
	 * Removes the given listener from the list of send listeners.
	 * 
	 * @param sendListener
	 *            the listener to be removed from the list of send listeners.
	 * @param clientSock
	 *            the client socket that identifies the client the listener
	 *            listens to.
	 */
	public synchronized void removeSpecificSendListener(
			final ISender sendListener, final Socket clientSock) {

		final ServerThread serverThread = this.clientTable.get(clientSock);
		if (serverThread != null) {
			serverThread.removeSendListener(sendListener);
		}
	}

	/**
	 * This is the server main loop. It waits for connects (if
	 * {@link #waitForConnects} is set), it works on the queue for uploads and
	 * sends all the packets to the clients and distributes received packets
	 * to
	 * the corresponding listeners.<br>
	 * Make sure to register all necessary listeners before starting the
	 * server
	 * (e.g. the connect listeners)... actually the following steps are
	 * necessary to start the server
	 * <ul>
	 * <li>call {@link #initServer()}</li>
	 * <li>call {@link #selectInterface(NetworkInterface, int)} (you can get
	 * the list of all interfaces by calling {@link #getInterfaces()})</li>
	 * <li>register connect listeners</li>
	 * <li>start the server thread!</li>
	 * </ul>
	 */
	public void run() {
		if (this.stillAlive) {
			return;
		}

		System.out.print("STARTING SERVER...");
		try {
			final ServerSocket socket = new ServerSocket();
			socket.setSoTimeout(100);
			socket.bind(new InetSocketAddress(this.listenAddress,
					this.listenPort));

			this.stillAlive = true;
			System.out.println("RUNNING!");
			while (this.stillAlive) {
				if (this.waitForConnects) {
					Socket newClient = null;
					try {
						newClient = socket.accept();

						System.out.println("SOCKET CONNECTED...");
						final ServerThread serverThread =
								this.connectClient(newClient);
						this.fireClientConnected(newClient);
						// Now start the processing.
						serverThread.start();

					} catch (final SocketTimeoutException ste) {
						// no client connected!
					}
				} else {
					try {
						Thread.sleep(100);
					} catch (final InterruptedException ire) {
						// do nothing ...
					}
				}
			}
			socket.close();
		} catch (final Exception e) {
			System.err.println("[OHOH:: NetworkServer.run() blew up!]");
			e.printStackTrace();
			this.criticalError = e;
		}
	}

	/**
	 * Select the network interface to start the server on.
	 * 
	 * @param nic
	 *            the network interface (card) that the server will open it's
	 *            port on.
	 * @param addressType
	 *            saves the type of address the server should use (currently
	 *            INET4 and INET6 are available) ({@link #ADDR_TYPE_INET4})
	 * @return <code>true</code> if the selected interface was selected with
	 *         the
	 *         given address type, <code>false</code> otherwise (for instance
	 *         if
	 *         inet6 is selected and no inet6 address is available on the
	 *         selected nic)
	 */
	public boolean selectInterface(final NetworkInterface nic,
			final int addressType) {
		final Enumeration<InetAddress> addrEnum = nic.getInetAddresses();
		this.listenAddress = null;

		while (addrEnum.hasMoreElements() && this.listenAddress == null) {
			final InetAddress next = addrEnum.nextElement();

			switch (addressType) {
			case ADDR_TYPE_INET4:
				if (next instanceof Inet4Address) {
					this.listenAddress = next;
				}
				break;

			case ADDR_TYPE_INET6:
				if (next instanceof Inet6Address) {
					this.listenAddress = next;
				}
				break;
			}
		}

		return this.listenAddress != null;
	}

	/**
	 * This method can be called to make all clients continue and quit the
	 * wait
	 * phase. This will start up- and downloads again.
	 */
	public synchronized void sendClientContinue() {
		final Enumeration<ServerThread> clientEnum =
				this.clientTable.elements();

		while (clientEnum.hasMoreElements()) {
			final ServerThread nextThread = clientEnum.nextElement();
			nextThread.doContinue();
		}

	}

	/**
	 * This method can be called to make all clients wait. While waiting no
	 * uploads or downloads are performed!
	 */
	public synchronized void sendClientWait() {
		final Enumeration<ServerThread> clientEnum =
				this.clientTable.elements();

		while (clientEnum.hasMoreElements()) {
			final ServerThread nextThread = clientEnum.nextElement();
			nextThread.doWait();
		}

	}

	/**
	 * Sets the port on which the networkserver is about to listen for
	 * incoming
	 * connections.
	 * 
	 * @param port
	 *            The port number to listen for incoming ocnnections.
	 */
	public void setServerPort(final int port) {
		assert port >= 0;
		if (!this.stillAlive) {
			this.listenPort = port;
		} else {
			throw new IllegalStateException(
					"Network server is already running.");
		}
	}

	/**
	 * call this method to tell the network server wether to listen for
	 * incoming
	 * client connects or not.
	 * 
	 * @param wait
	 *            <code>true</code> to make the server listen for incoming
	 *            connects, <code>false</code> to ignore all incoming connects
	 */
	public void setWaitForConnects(final boolean wait) {
		this.waitForConnects = wait;
	}
}
